.. _глава_48_start:

Переменное количество параметров
================================

В этой главе рассматриваются две функции D, обеспечивающие гибкость механизма передачи параметров при
при вызове функций:
* Аргументы по умолчанию
* Вариативные функции

Аргументы по умолчанию
----------------------

Удобство работы с параметрами функций заключается в возможности задавать для них значения по умолчанию
для них. Это похоже на начальные значения по умолчанию для членов struct.
Некоторые параметры некоторых функций вызываются в основном с одними и теми же
значениями. Чтобы увидеть пример этого, рассмотрим функцию, которая печатает элементы
ассоциативного массива типа string[string]. Предположим, что функция
принимает в качестве параметров также символы-разделители:

.. code-block:: d

        import std.algorithm;

        // ...
        void printAA(string title,
            string[string] aa,
            string keySeparator,
            string elementSeparator)
        {
            writeln("-- ", title, " --");
            auto keys = sort(aa.keys);
            // Don't print element separator before the first element
            if (keys.length != 0)
            {
                auto key = keys[0];
                write(key, keySeparator, aa[key]);
                keys = keys[1 .. $]; // Remove the first element
            }
            // Print element separator before the remaining elements
            foreach (key; keys)
            {
                write(elementSeparator);
                write(key, keySeparator, aa[key]);
            }
            writeln();
        }

Эта функция вызывается ниже с ":" в качестве разделителя ключей и ", " в качестве
разделитель элементов:

.. code-block:: d

    void main()
    {
        string[string] dictionary = ["blue":"mavi", "red":"kırmızı", "gray":"gri" ];
        printAA("Color Dictionary", dictionary, ":", ", ");
    }


Вывод:

.. code-block:: d

    -- Color Dictionary --
    blue:mavi, gray:gri, red:kırmızı


Если разделителями почти всегда будут эти два, их можно определить
со значениями по умолчанию:


.. code-block:: d

    void printAA(string title,
        string[string] aa,
        string keySeparator = ": ",
        string elementSeparator = ", ")
    {
    // ...
    }

Параметры со значениями по умолчанию не нужно указывать при вызове функции:

.. code-block:: d

    printAA("Color Dictionary",dictionary);
    /* ← No separator specified. Both
     * parameters will get their
     * default values. */

Значения параметров все еще могут быть указаны при необходимости, но не обязательно все:

.. code-block:: d

    printAA("Color Dictionary", dictionary, "=");

.. code-block:: d

    -- Color Dictionary --
    blue=mavi, gray=gri, red=kırmızı

В следующем вызове указываются оба параметра:

.. code-block:: d


В следующем вызове указываются оба параметра:

.. code-block:: d

    printAA("Color Dictionary", dictionary, "=", "\n");

Вывод

.. code-block:: d

    -- Color Dictionary --
        blue=mavi, gray=gri, red=kırmızı

Значения по умолчанию можно задать только для тех параметров, которые находятся в конце
списка параметров.

.. index::
    __MODULE__
    __FILE__
    __FILE_FULL_PATH__
    __LINE__
    __FUNCTION__
    __PRETTY_FUNCTION__

**Специальные ключевые слова в качестве аргументов по умолчанию**

Следующие специальные ключевые слова действуют как литералы времени компиляции, имеющие значения
соответствующие тому, где они появляются в коде:
* __MODULE__: Имя модуля в виде строки
* __FILE__: Имя исходного файла в виде строки
* __FILE_FULL_PATH__: Имя исходного файла, включая полный путь к нему, как   строка
* __LINE__: Номер строки как int
* __FUNCTION__: Имя функции в виде строки
* __PRETTY_FUNCTION__: Полная сигнатура функции в виде строки
Хотя они могут быть полезны в любом месте кода, они работают по-другому, когда используются
в качестве аргументов по умолчанию. Когда они используются в обычном коде, их значения относятся к
где они появляются в коде:

.. code-block:: d

    import std.stdio;

    void func(int parameter)
    {
        writefln("Inside function %s at file %s, line %s.",__FUNCTION__, __FILE__, __LINE__); // ← line 5
    }

Печатаемая  строка 5 находится внутри функции: func в файле deneme.d, строка 5.
Однако иногда интереснее определить строку, с которой функция
вызывается, а не то, где находится определение функции. Когда эти специальные
ключевые слова предоставляются в качестве аргументов по умолчанию, их значения указывают на то, откуда
функция вызывается из строки:

.. code-block:: d

    import std.stdio;
    void func(int parameter,string functionName = __FUNCTION__,string file = __FILE__,int line = __LINE__)
    {
        writefln("Called from function %s at file %s, line %s.",functionName, file, line);
    }

    void main()
    {
        func(42); // ← line 12
    }

На этот раз специальные ключевые слова относятся к main(), вызывающей функцию

.. code-block:: console

    Вызывается из функции deneme.main в файле deneme.d, строка 12

Помимо вышеперечисленных, существуют также следующие специальные лексемы, которые принимают значения
в зависимости от компилятора и времени суток:
* __DATE__: Дата компиляции в виде строки
* __TIME__: Время компиляции в виде строки
* __TIMESTAMP__: Дата и время компиляции в виде строки
* __VENDOR__: Поставщик компилятора в виде строки (например, "Digital Mars D")
* __VERSION__: Версия компилятора в виде long (например, значение 2081L для версии 2.081)

.. index::
    вариативные функции

Вариативные функции (функции с переменным числом параметров)
------------------------------------------------------------

Значения параметров по умолчанию не изменяют количество
параметров, которые получает функция. Например, несмотря на то, что некоторым параметрам
могут иметь значения по умолчанию, функция printAA() всегда принимает четыре параметра
и использует их в соответствии со своей реализацией.
С другой стороны, вариативные функции могут вызываться с неопределенным количеством
аргументов. Мы уже использовали эту возможность с помощью функций
например, writeln(). writeln() можно вызывать с любым количеством аргументов

.. code-block:: d

    writeln("hello", 7, "world", 9.8 /*, and any number of other arguments as needed */);

Существует четыре способа определения переменных функций в D:

* Функция, которая работает только для функций, помеченных как extern(C). Эта
  определяет скрытую переменную _argptr, которая используется для доступа к
  параметрам. В данной книге эта функция не рассматривается, отчасти потому, что она небезопасна.
* Функция, работающая с обычными функциями D, которая также использует скрытую переменную
  _argptr, а также переменная _arguments, причем последняя имеет тип
  TypeInfo[]. В этой книге эта функция не рассматривается, поскольку она
  опирается на указатели, которые еще не рассматривались, и потому, что эта функция
  может быть использована небезопасным способом.
* Безопасная функция с ограничением, что неопределенное количество параметров
  должны быть одного типа. Именно эта функция рассматривается в данном разделе.
* Неопределенное количество параметров шаблона. Эта функция будет описана
  позже в главах, посвященных шаблонам.

Параметры вариативных функций передаются в функцию в виде среза.
Вариативные функции определяются с одним параметром определенного типа slice
за которым сразу же следуют символы ...

.. code-block:: d

    double sum(double[] numbers...)
    {
        double result = 0.0;
        foreach (number; numbers)
        {
            result += number;
        }

        return result;
    }

Это определение делает sum() вариативной функцией, то есть она может принимать
любое количество аргументов, если они являются double или любым другим типом, который может
неявно преобразуется в double

.. code-block:: d

    writeln(sum(1.1, 2.2, 3.3));

Единственный параметр slice и символы ... представляют все аргументы.
Например, срез будет состоять из пяти элементов, если функция вызывается с
пятью двойными значениями.
На самом деле переменное количество параметров также может быть передано в виде одного среза

.. code-block:: d

    writeln(sum([ 1.1, 2.2, 3.3 ]));


Вариативные функции также могут иметь обязательные параметры, которые должны быть определены
первыми в списке параметров. Например, следующая функция выводит
неопределенное количество параметров в круглых скобках. Хотя функция
оставляет количество элементов гибким, она требует, чтобы круглые скобки были
всегда были указаны:

.. code-block:: d

    char[] parenthesize(string opening, // ← The first two parameters must be string closing,
    // specified when the function is called
    string[] words...)
    { // ← Need not be specified
        char[] result;
        foreach (word; words)
        {
            result ~= opening;
            result ~= word;
            result ~= closing;
        }

        return result;
    }


Первые два параметра являются обязательными:

.. code-block:: d

    parenthesize("{"); // ← ошибка компиляции

Если указаны обязательные параметры, остальные являются необязательными:
.. code-block:: d

        writeln(parenthesize("{", "}", "яблоко", "груша", "банан"));

Вывод:

.. code-block:: d

    apple}{pear}{banana}

**Аргументы вариативных функций имеют короткое время жизни**

Аргумент slice, который автоматически генерируется для переменного параметра
указывает на временный массив, который имеет короткое время жизни. Этот факт не имеет значения, если
функция использует аргументы только во время своего выполнения. Однако было бы ошибкой, если бы
ошибка, если функция сохранит срез этих элементов для последующего использования:


.. code-block:: d

    int[] numbersForLaterUse;
    void foo(int[] numbers...)
    {
        numbersForLaterUse = numbers; // ← BUG
    }

    struct S
    {
        string[] namesForLaterUse;
        void foo(string[] names...)
        {
            namesForLaterUse = names; // ← BUG
        }
    }

    void bar()
    {
        foo(1, 10, 100); /* The temporary array [ 1, 10, 100 ] is
                            * not valid beyond this point. */
        auto s = S();
        s.foo("hello", "world"); /* The temporary array
                                        * [ "hello", "world" ] is not
                                        * valid beyond this point. */
        // ...
    }

    void main()
    {
        bar();
    }


И отдельно стоящая функция foo(), и функция-член S.foo() являются ошибочными.
потому что они сохраняют фрагменты в автоматически сгенерированных временных массивах, которые
которые находятся в стеке программы. Эти массивы действительны только во время выполнения
переменных функций.
По этой причине, если функции нужно сохранить фрагмент элементов переменного
параметра, она должна сначала получить копию этих элементов:

.. code-block:: d

    void foo(int[] numbers...)
    {
        numbersForLaterUse = numbers.dup; // ← correct
    }
    // ...

    void foo(string[] names...)
    {
        namesForLaterUse = names.dup; // ← correct
    }

Однако, поскольку переменные функции можно вызывать и с фрагментами соответствующих массивов,
копирование элементов в этих случаях будет излишним.
Правильным и эффективным решением является определение двух функций с одинаковым именем
одно и то же имя, одна из которых принимает переменный параметр, а другая - правильный срез. Если вызывающая сторона передает переменное количество аргументов, то вызывается переменная версия
функции; а если вызывающая сторона передает правильный срез, то вызывается версия
которая принимает правильный срез:

.. code-block:: d

    int[] numbersForLaterUse;
    void foo(int[] numbers...)
    {
        /* Since this is the variadic version of foo(), we must
            * first take a copy of the elements before storing a
            * slice to them. */
        numbersForLaterUse = numbers.dup;
    }

    void foo(int[] numbers)
    {
        /* Since this is the non-variadic version of foo(), we can
            * store the slice as is. */
        numbersForLaterUse = numbers;
    }

    struct S
    {
        string[] namesForLaterUse;
        void foo(string[] names...)
        {
            /* Since this is the variadic version of S.foo(), we
            * must first take a copy of the elements before
            * storing a slice to them. */
            namesForLaterUse = names.dup;
        }

        void foo(string[] names)
        {
            /* Since this is the non-variadic version of S.foo(),
            * we can store the slice as is. */
            namesForLaterUse = names;
        }
    }

    void bar()
    {
        // This call is dispatched to the variadic function.
        foo(1, 10, 100);
        // This call is dispatched to the proper slice function.
        foo([2, 20, 200]);
        auto s = S();
        // This call is dispatched to the variadic function.
        s.foo("hello", "world");
        // This call is dispatched to the proper slice function.
        s.foo(["hi", "moon"]);
        // ...
    }

    void main()
    {
        bar();
    }

Определение нескольких функций с одним и тем же именем, но с разными параметрами называется
называется перегрузкой функций, о которой пойдет речь в следующей главе

Упражнения
----------

Предположим, что следующее перечисление уже определено:

.. code-block:: d

    enum Operation { add, subtract, multiply, divide }

Также предположим, что существует структура, представляющая вычисление операции
и двух ее операндов:

.. code-block:: d

    struct Calculation
    {
        Operation op;
        double first;
        double second;
    }

Например, объект Calculation(Operation.divide, 7.7, 8.8) будет представлять собой
представлять деление 7,7 на 8,8.
Разработайте функцию, которая получает неопределенное количество этих объектов struct,
вычисляет результат каждого Calculation, а затем возвращает все результаты в виде
фрагмента типа double[].
Например, функцию можно вызвать так, как показано в следующем коде:

.. code-block:: d

    void main()
    {
        writeln(Calculation(Calculation(Operation.add, 1.1, 2.2),
                Calculation(Operation.subtract, 3.3, 4.4),
                Calculation(Operation.multiply, 5.5, 6.6),
                Calculation(Operation.divide, 7.7, 8.8)));
    }

Вывод кода должен быть похож на следующий:

.. code-block:: d

    [3.3, -1.1, 36.3, 0.875]

Решение :ref:`Переменное число параметров<Переменное число параметров>`
